/***************************************************************************
 *   Antc - library                                                        *
 *                                                                         *
 *   Copyright (C) 2008 by kuerant                                         *
 *                         <kuerant@gmail.com>                             *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This library is distributed in the hope that it will be usefull, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
 *   Library General Public License for more details.                      *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston,                *
 *   MA 02111-1307, USA                                                    *
 ***************************************************************************/


#ifndef ANT_PACKET_HPP
#define ANT_PACKET_HPP

#include    "help/string.hpp"
#include    "common/exception.hpp"

namespace   ant
{

class   Packet
{
public  :
    typedef unsigned    serial_type;
    typedef unsigned    source_type;
    typedef unsigned    target_type;
    
    enum    {
        HEAD_SIZE = (3 * sizeof(unsigned) + sizeof(source_type) + sizeof(target_type)),
    };
        
public  :
    Packet(unsigned iCapacity=65536) 
        :   m_iCapacity(iCapacity), m_iHeadSize(0),
            m_piSerial(NULL), m_piType(NULL), m_piSerialFor(NULL), 
            m_pSource(NULL), m_pTarget(NULL),
            m_iBodySize(0), m_pvBody(NULL),
            m_pszChunk(NULL)
    {
        if ( m_iCapacity < HEAD_SIZE ) {
            m_iCapacity = HEAD_SIZE;
        }

        m_pszChunk = new char[m_iCapacity];
        void*   _pvChunk = m_pszChunk;
        unsigned*   _pi = static_cast<unsigned *>(_pvChunk);
        m_piSerial    = _pi + 0;
        m_piType      = _pi + 1;
        m_piSerialFor = _pi + 2;

        void*   _pvSerialFor = m_piSerialFor;
        void*   _pvSource    = (static_cast<char*>(_pvSerialFor) + sizeof(unsigned));
        m_pSource            = static_cast<source_type *>(_pvSource);
        void*   _pvTarget    = (static_cast<char*>(_pvSource) + sizeof(source_type));
        m_pTarget            = static_cast<target_type *>(_pvTarget);

        m_iHeadSize = HEAD_SIZE;

        m_pvBody = static_cast<void *>(m_pszChunk + m_iHeadSize);
        memset( m_pszChunk, 0, m_iHeadSize );
    }
    
    Packet(std::string& strPacket) 
        :   m_iCapacity(strPacket.size()), m_iHeadSize(0),
            m_piSerial(NULL), m_piType(NULL), m_piSerialFor(NULL), 
            m_pSource(NULL), m_pTarget(NULL),
            m_iBodySize(0), m_pvBody(NULL),
            m_pszChunk(NULL)
    {
        if ( m_iCapacity < HEAD_SIZE ) {
            m_iCapacity = HEAD_SIZE;
        }
        
        m_pszChunk = new char[m_iCapacity];
        void*   _pvChunk = m_pszChunk;
        unsigned*   _pi = static_cast<unsigned *>(_pvChunk);
        m_piSerial    = _pi + 0;
        m_piType      = _pi + 1;
        m_piSerialFor = _pi + 2;
        
        void*   _pvSerialFor = m_piSerialFor;
        void*   _pvSource    = (static_cast<char*>(_pvSerialFor) + sizeof(unsigned));
        m_pSource            = static_cast<source_type *>(_pvSource);
        void*   _pvTarget    = (static_cast<char*>(_pvSource) + sizeof(source_type));
        m_pTarget            = static_cast<target_type *>(_pvTarget);

        m_iHeadSize = HEAD_SIZE;

        m_pvBody = static_cast<void *>(m_pszChunk + m_iHeadSize);

        memcpy(m_pszChunk, strPacket.c_str(), strPacket.size());
    }
    
    ~Packet()
    {
        if (NULL != m_pszChunk) {
            delete [] m_pszChunk;
        }
    }

public  :
    struct  packet_id_type
    {
    public  :
        packet_id_type(source_type source, serial_type serial)
            :   m_source(source), m_serial(serial)
        {
            //fprintf(stderr, "%s %d\n", __FILE__, __LINE__);
        }
        
        packet_id_type(const packet_id_type& rhs)
            :   m_source(rhs.m_source), m_serial(rhs.m_serial)
        {
            //fprintf(stderr, "%s %d\n", __FILE__, __LINE__);
        }
        
    public  :
        packet_id_type& operator=(const packet_id_type& rhs)
        {
            m_source = rhs.m_source;
            m_serial = rhs.m_serial;
        }
        
        bool    operator==(const packet_id_type& rhs) const
        {
            return  ((m_source == rhs.m_source) && (m_serial == rhs.m_serial));
        }
        
        bool    operator!=(const packet_id_type& rhs) const
        {
            return  ((m_source != rhs.m_source) || (m_serial != rhs.m_serial));
        }
        
        bool    operator<(const packet_id_type& rhs) const
        {
            if (m_source < rhs.m_source) {
                return  true;
            }
            else if (m_source == rhs.m_source) {
                return  (m_serial < rhs.m_serial);
            }
            
            return  false;
        }
        
        bool    empty(void) const 
        {
            return  ((0 == m_source) || (0 == m_serial));
        }
        
    private :
        packet_id_type();
        
    private :
        source_type     m_source;
        serial_type     m_serial;
    };
    typedef struct packet_id_type   packet_id_type;
    
    packet_id_type  getID(void) const 
    {
        return  packet_id_type(*m_pSource, *m_piSerial);
    }
    
public  :

    Packet& operator=(const Packet& rhs)
    {
        if ( this->m_iCapacity < rhs.m_iCapacity ) {
            delete [] m_pszChunk;
            
            m_pszChunk = new char[ rhs.m_iCapacity ];
            m_iCapacity = rhs.m_iCapacity;
        }
        
        this->m_iBodySize = rhs.m_iBodySize;
        memcpy(this->m_pszChunk, rhs.m_pszChunk, rhs.m_iBodySize);
    }
        
public  :
    unsigned    getCapacity(void) const { return m_iCapacity; }
    unsigned    getBodyCapacity(void) const { return m_iCapacity - m_iHeadSize; }
    
    unsigned    getPackSize(void) const { return m_iHeadSize + m_iBodySize; }
    void*       getPack(void) { return m_pszChunk; }
    
    void    clear(void)
    {
        *m_piSerial = 0;
        *m_piType = 0;
        *m_piSerialFor = 0;
        *m_pSource = 0;
        *m_pTarget = 0;
    }

    enum packet_type
    {
        UNKNOWN_PACKET  = 0x0000,
        DATA_PACKET     = 0x0901,
        ACK_PACKET      = 0x0902,
        SYN_PACKET      = 0x0903
    };
        
    serial_type getSerial(void)     const { return *m_piSerial; }
    unsigned    getType(void)       const { return *m_piType; }
    serial_type getSerialFor(void)  const { return *m_piSerialFor; }
    
    bool    isDataPacket(void)      const { return *m_piType == DATA_PACKET; }
    bool    isAckPacket(void)       const { return *m_piType == ACK_PACKET; }
    bool    isSynPacket(void)       const { return *m_piType == SYN_PACKET; }
    void    setDataPacketType(void)       { *m_piType = DATA_PACKET; }
    void    setAckPacketType(void)        { *m_piType = ACK_PACKET; }
    void    setSynPacketType(void)        { *m_piType = SYN_PACKET; }
    
    void    setSerial(void)     { *m_piSerial = serialize(); }
    void    setType(unsigned iType) { *m_piType = iType; }
    void    setSerialFor(serial_type iSerialFor)   { *m_piSerialFor = iSerialFor; }
    
    source_type    getSource(void)    const { return *m_pSource; }
    target_type    getTarget(void)    const { return *m_pTarget; }
    
    void        setSource(source_type source)  { *m_pSource = source; }
    void        setTarget(target_type target)  { *m_pTarget = target; }
    
    static  unsigned    serialize(void) 
    {
        #define __MIN_PACKET_SERIAL 1
        #define __MAX_PACKET_SERIAL (1 << 24)
        static  unsigned    s_iSerial = __MIN_PACKET_SERIAL;
        
        unsigned    iSerial = s_iSerial;
        ++s_iSerial;
        if (s_iSerial > __MAX_PACKET_SERIAL) {
            s_iSerial = __MIN_PACKET_SERIAL;
        }
        
        return  iSerial;
    }
    
    void*   getBody(void)    { return m_pvBody; }
    void    setBody(const void* pvBody, unsigned iBodySize) 
    {
        if ((iBodySize + m_iHeadSize) > m_iCapacity) {
            throw Exception(help::String::format("Packet Body Overflow (body-capacity=%u data-size=%u).", (m_iCapacity - m_iHeadSize), iBodySize), ANTC_EOVERFLOW);
        }
        
        memmove(m_pvBody, pvBody, iBodySize);
        m_iBodySize = iBodySize;
    }
    
    std::string toString(void)
    {
        return  help::String::format("PACKET(serial=%u type=0x%04X(%s) snfor=%u)", *m_piSerial, *m_piType, getTypeName(*m_piType), *m_piSerialFor);
    }
    
private :
    const char*     getTypeName(unsigned iType)
    {
        switch( iType ) {
        case    DATA_PACKET   :   return  "DATA";
        case    ACK_PACKET    :   return  "ACK";
        case    SYN_PACKET    :   return  "SYN";
        }
    }
    
private :
    unsigned    m_iCapacity;

    unsigned    m_iHeadSize;
    serial_type*   m_piSerial;
    serial_type*   m_piSerialFor;
    unsigned*   m_piType;

    source_type*   m_pSource;
    target_type*   m_pTarget;
    
    unsigned    m_iBodySize;
    void*       m_pvBody;
    
    char*       m_pszChunk;
};  // class    Packet
    
}   // namespace ant

#endif  // ANT_PACKET_HPP
